package algorithm.sort.bubble;

import algorithm.sort.base.ArrayUtils;

import java.util.ArrayDeque;
import java.util.Deque;

public class StateEventBubbleSort2 {
    public static void sort(int[] data) {
        Deque<Event> eventDeque = new ArrayDeque<>();
        eventDeque.push(new InitEvent(data));
        while (!eventDeque.isEmpty()) {
            Event event = eventDeque.pop();
            switch (event) {
                case InitEvent initEvent -> {
                    if (initEvent.getSource() != null) {
                        int[] source = initEvent.getSource();
                        eventDeque.push(new SortIndexEvent(source, source.length - 1));
                    }
                }
                case SortIndexEvent sortIndexEvent -> {
                    int[] source = sortIndexEvent.getSource();
                    int index = sortIndexEvent.getIndex();
                    if (index > 0) {
                        eventDeque.push(new SortIndexEvent(source, index - 1));
                        eventDeque.push(new BubbleMaxToIndexEvent(source, index));
                    }
                }
                case BubbleMaxToIndexEvent bubbleMaxToIndexEvent -> {
                    int[] source = bubbleMaxToIndexEvent.getSource();
                    int index = bubbleMaxToIndexEvent.getIndex();
                    if (index > 0) {
                        eventDeque.push(new CompareAndSwapUpwardEvent(source, index - 1, index));
                        eventDeque.push(new BubbleMaxToIndexEvent(source, index - 1));
                    }
                }
                case CompareAndSwapUpwardEvent compareAndSwapUpwardEvent -> {
                    int[] source = compareAndSwapUpwardEvent.getSource();
                    int firstIndex = compareAndSwapUpwardEvent.getFirstIndex();
                    int secondIndex = compareAndSwapUpwardEvent.getSecondIndex();
                    if (source[firstIndex] > source[secondIndex]) {
                        ArrayUtils.swap(source, firstIndex, secondIndex);
                    }
                }
                default -> throw new IllegalStateException("Unexpected value: " + event);
            }
        }
    }

    private static abstract class Event {
        int[] source;

        public Event(int[] source) {
            this.source = source;
        }

        public int[] getSource() {
            return this.source;
        }
    }

    private static class InitEvent extends Event {
        public InitEvent(int[] source) {
            super(source);
        }
    }

    private static class SortIndexEvent extends Event {
        int index;

        public SortIndexEvent(int[] source, int index) {
            super(source);
            this.index = index;
        }

        public int getIndex() {
            return index;
        }
    }

    private static class BubbleMaxToIndexEvent extends Event {
        int index;

        public BubbleMaxToIndexEvent(int[] source, int index) {
            super(source);
            this.index = index;
        }

        public int getIndex() {
            return index;
        }
    }

    private static class CompareAndSwapUpwardEvent extends Event {
        int firstIndex;
        int secondIndex;
        public CompareAndSwapUpwardEvent(int[] source, int firstIndex, int secondIndex) {
            super(source);
            this.firstIndex = firstIndex;
            this.secondIndex = secondIndex;
        }
        public int getFirstIndex() {
            return firstIndex;
        }
        public int getSecondIndex() {
            return secondIndex;
        }
    }
}
